/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */
package org.jake.nb.ext.ant.hints.ui;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.prefs.AbstractPreferences;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.JComboBox;
import javax.swing.JEditorPane;
import javax.swing.JTree;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import org.jake.nb.ext.ant.hints.AntHint;
import org.netbeans.spi.editor.hints.Severity;

import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;

/** Contains all important listeners and logic of the Hints Panel.
 *
 * @author Petr Hrebejk
 */
class HintsPanelLogic implements MouseListener, KeyListener, TreeSelectionListener, ChangeListener, ActionListener {

  private Map<AntHint, ModifiedPreferences> changes;
  private static final String DESCRIPTION_HEADER =
          "<html><head>" + // NOI18N
          //"<link rel=\"StyleSheet\" href=\"nbdocs://org.netbeans.modules.usersguide/org/netbeans/modules/usersguide/ide.css\" type=\"text/css\">" // NOI18N
          //"<link rel=\"StyleSheet\" href=\"nbresloc:/org/netbeans/modules/java/hints/resources/ide.css\" type=\"text/css\">" + // NOI18N
          "</head><body>"; // NOI18N
  private static final String DESCRIPTION_FOOTER = "</body></html>"; // NOI18N
  private JTree errorTree;
  private JComboBox severityComboBox;
  private JEditorPane descriptionTextArea;

  HintsPanelLogic() {
    changes = new HashMap<AntHint, ModifiedPreferences>();
  }

  void connect(JTree errorTree, JComboBox severityComboBox, JEditorPane descriptionTextArea) {

    this.errorTree = errorTree;
    this.severityComboBox = severityComboBox;
    this.descriptionTextArea = descriptionTextArea;

    valueChanged(null);

    errorTree.addKeyListener(this);
    errorTree.addMouseListener(this);
    errorTree.getSelectionModel().addTreeSelectionListener(this);

    severityComboBox.addActionListener(this);

  }

  void disconnect() {

    errorTree.removeKeyListener(this);
    errorTree.removeMouseListener(this);
    errorTree.getSelectionModel().removeTreeSelectionListener(this);

    severityComboBox.removeActionListener(this);

    componentsSetEnabled(false);
  }

  synchronized void applyChanges() {
    for (AntHint hint : changes.keySet()) {
      ModifiedPreferences mn = changes.get(hint);
      mn.store(hint.getConfiguration().getPreferences());
    }
  }

  /** Were there any changes in the settings
   */
  boolean isChanged() {
    return !changes.isEmpty();
  }

  synchronized Preferences getCurrentPrefernces(AntHint hint) {
    Preferences node = changes.get(hint);
    return node == null ? hint.getConfiguration().getPreferences() : node;
  }

  synchronized Preferences getPreferences4Modification(AntHint hint) {
    Preferences node = changes.get(hint);
    if (node == null) {
      node = new ModifiedPreferences(hint.getConfiguration().getPreferences());
      changes.put(hint, (ModifiedPreferences) node);
    }
    return node;
  }

  static Object getUserObject(TreePath path) {
    if (path == null) {
      return null;
    }
    DefaultMutableTreeNode tn = (DefaultMutableTreeNode) path.getLastPathComponent();
    return tn.getUserObject();
  }

  static Object getUserObject(DefaultMutableTreeNode node) {
    return node.getUserObject();
  }

  /**
   * Checks whether the folder node is selected or not - this means to traverse all childs of the folder and if only
   * one of them is selected, the folder is selected either
   * @param node
   * @return
   */
  boolean isSelected(DefaultMutableTreeNode node) {
    for (int i = 0; i < node.getChildCount(); i++) {
      DefaultMutableTreeNode ch = (DefaultMutableTreeNode) node.getChildAt(i);
      Object o = ch.getUserObject();
      if (o instanceof AntHint) {
        AntHint hint = (AntHint) o;
        if (hint.getConfiguration().isEnabled(getCurrentPrefernces(hint))) {
          return true;
        }
      }
    }
    return false;
  }

  // MouseListener implementation --------------------------------------------
  @Override
  public void mouseClicked(MouseEvent e) {
    Point p = e.getPoint();
    TreePath path = errorTree.getPathForLocation(e.getPoint().x, e.getPoint().y);
    if (path != null) {
      Rectangle r = errorTree.getPathBounds(path);
      if (r != null) {
        r.width = r.height;
        if (r.contains(p)) {
          toggle(path);
        }
      }
    }
  }

  @Override
  public void mouseEntered(MouseEvent e) {
  }

  @Override
  public void mouseExited(MouseEvent e) {
  }

  @Override
  public void mousePressed(MouseEvent e) {
  }

  @Override
  public void mouseReleased(MouseEvent e) {
  }

  // KeyListener implementation ----------------------------------------------
  @Override
  public void keyTyped(KeyEvent e) {
  }

  @Override
  public void keyReleased(KeyEvent e) {
  }

  @Override
  public void keyPressed(KeyEvent e) {
    if (e.getKeyCode() == KeyEvent.VK_SPACE || e.getKeyCode() == KeyEvent.VK_ENTER && e.getSource() instanceof JTree) {

      JTree tree = (JTree) e.getSource();
      TreePath path = tree.getSelectionPath();

      if (toggle(path)) {
        e.consume();
      }
    }
  }

  // TreeSelectionListener implementation ------------------------------------
  @Override
  public void valueChanged(TreeSelectionEvent ex) {
    Object o = getUserObject(errorTree.getSelectionPath());

    if (o instanceof AntHint) {
      AntHint hint = (AntHint) o;

      // Enable components
      componentsSetEnabled(true);

      // Set proper values to the componetnts

      Preferences p = getCurrentPrefernces(hint);

      Severity severity = hint.getConfiguration().getSeverity(p);
      if (severity != null) {
        severityComboBox.setSelectedItem(severity);
        severityComboBox.setEnabled(true);
      } else {
        severityComboBox.setSelectedItem(Severity.ERROR);
        severityComboBox.setEnabled(false);
      }

      String description = hint.getConfiguration().getDescription();
      descriptionTextArea.setText(description == null ? "" : wrapDescription(description)); // NOI18N

    } else { // Category or nonsense selected.
      componentsSetEnabled(false);
    }
  }

  // ActionListener implementation -------------------------------------------
  @Override
  public void actionPerformed(ActionEvent e) {
    if (errorTree.getSelectionPath() == null) {
      return;
    }

    Object o = getUserObject(errorTree.getSelectionPath());

    if (o instanceof AntHint) {
      AntHint hint = (AntHint) o;
      Preferences p = getPreferences4Modification(hint);

      if (hint.getConfiguration().getSeverity(p) != null && severityComboBox.equals(e.getSource())) {
        hint.getConfiguration().setSeverity(p, (Severity) severityComboBox.getSelectedItem());
      }
    }
  }

  // ChangeListener implementation -------------------------------------------
  @Override
  public void stateChanged(ChangeEvent e) {
  }

  // Private methods ---------------------------------------------------------
  private String wrapDescription(String description) {
    return new StringBuffer(DESCRIPTION_HEADER).append(description).append(DESCRIPTION_FOOTER).toString();
  }

  private boolean toggle(final TreePath treePath) {

    if (treePath == null) {
      return false;
    }

    Object o = getUserObject(treePath);

    DefaultTreeModel model = (DefaultTreeModel) errorTree.getModel();
    DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();


    if (o instanceof AntHint) {
      AntHint hint = (AntHint) o;
      boolean value = !hint.getConfiguration().isEnabled(getCurrentPrefernces(hint));
      Preferences mn = getPreferences4Modification(hint);
      hint.getConfiguration().setEnabled(mn, value);
      model.nodeChanged(node);
      model.nodeChanged(node.getParent());
    } else if (o instanceof String) {
      boolean value = !isSelected(node);

      for (int i = 0; i < node.getChildCount(); i++) {
        DefaultMutableTreeNode ch = (DefaultMutableTreeNode) node.getChildAt(i);
        Object cho = ch.getUserObject();
        if (cho instanceof AntHint) {
          AntHint hint = (AntHint) cho;
          boolean cv = hint.getConfiguration().isEnabled(getCurrentPrefernces(hint));
          if (cv != value) {
            Preferences mn = getPreferences4Modification(hint);
            hint.getConfiguration().setEnabled(mn, value);
            model.nodeChanged(ch);
          }
        }
      }
      model.nodeChanged(node);
    }

    return false;
  }

  private void componentsSetEnabled(boolean enabled) {

    if (!enabled) {
      severityComboBox.setSelectedItem(Severity.WARNING);
      descriptionTextArea.setText(""); // NOI18N
    }

    severityComboBox.setEnabled(enabled);
    descriptionTextArea.setEnabled(enabled);
  }

  // Inner classes -----------------------------------------------------------
  private static class ModifiedPreferences extends AbstractPreferences {

    private Map<String, Object> map = new HashMap<String, Object>();

    public ModifiedPreferences(Preferences node) {
      super(null, ""); // NOI18N
      try {
        for (java.lang.String key : node.keys()) {
          put(key, node.get(key, null));
        }
      } catch (BackingStoreException ex) {
        Exceptions.printStackTrace(ex);
      }
    }

    public void store(Preferences target) {

      try {
        for (String key : keys()) {
          target.put(key, get(key, null));
        }
      } catch (BackingStoreException ex) {
        Exceptions.printStackTrace(ex);
      }

    }

    @Override
    protected void putSpi(String key, String value) {
      map.put(key, value);
    }

    @Override
    protected String getSpi(String key) {
      return (String) map.get(key);
    }

    @Override
    protected void removeSpi(String key) {
      map.remove(key);
    }

    @Override
    protected void removeNodeSpi() throws BackingStoreException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    protected String[] keysSpi() throws BackingStoreException {
      String array[] = new String[map.keySet().size()];
      return map.keySet().toArray(array);
    }

    @Override
    protected String[] childrenNamesSpi() throws BackingStoreException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    protected AbstractPreferences childSpi(String name) {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    protected void syncSpi() throws BackingStoreException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    protected void flushSpi() throws BackingStoreException {
      throw new UnsupportedOperationException("Not supported yet.");
    }
  }
}
